/*
 * FileReader.cpp
 *
 *  Created on: Aug 31, 2013
 *      Author: Jeff
 */

#include "FileReader.h"
#include "RawFileComparisor.h"

#include "exception.h"

namespace fs {

FileReader::FileReader(const char* file):
		currentSegment(0),
		offset(0),
		delete_length(0),
		insert_length(0),
		fs_base(NULL),
		cursor_diff(NULL)
{
	Json::Value root;
	if(!readJsonFromFile(file, &root))
	{
		std::fstream entity;
		entity.open(file, std::ios::in | std::ios::binary);
		entity.seekg(0, std::ios::end);

		Json::Value segment;
		segment[JSON_KEY_INSERT_LENGTH] = (long long)entity.tellg();
		segment[JSON_KEY_DELETE_LENGTH] = 0;
		segment[JSON_KEY_OFFSET] = 0;

		root[JSON_KEY_SEGMENT].append(segment);
		root[JSON_KEY_SEGMENT_FILE] = file;

		entity.close();
	}

	init(root);
}

FileReader::FileReader(Json::Value info):
		currentSegment(0),
		offset(0),
		delete_length(0),
		insert_length(0),
		fs_base(NULL),
		cursor_diff(NULL)
{
	init(info);
}

bool FileReader::readJsonFromFile(const char* file, Json::Value* json)
{
	std::fstream json_stream;
	json_stream.open(file, std::ios::in);

	Json::Reader reader;
	return reader.parse(json_stream, *json, false);
}

void FileReader::init(Json::Value json)
{
	this->info = json;
	if(info.isNull())
	{
		NULLPointerException e("Json object cannot be NULL");
		throw e;
	}

	this->segments = info[JSON_KEY_SEGMENT];

	std::string fileName = info[JSON_KEY_RIGHT_FILE].asString();
	if(fileName.length() > 0)
		fs_base = new FileReader(fileName.c_str());

	std::string diff_file = info[JSON_KEY_SEGMENT_FILE].asString();
	if(diff_file.length() > 0)
	{
		fs_diff.open(diff_file.c_str(), std::ios::in | std::ios::binary);
		cursor_diff = fs_diff.rdbuf();
	}
}

FileReader::~FileReader() {
	if(NULL != fs_diff && fs_diff.is_open())
		fs_diff.close();

	if(NULL != fs_base)
		delete fs_base;
}

bool FileReader::loadSegment()
{
	while(endOfSegment())
	{
		if(segments.size() <= currentSegment)
		{
			return false;
		}

		Json::Value segment = segments[currentSegment];

		offset = segment[JSON_KEY_OFFSET].asUInt64();
		delete_length = segment[JSON_KEY_DELETE_LENGTH].asInt();
		insert_length = segment[JSON_KEY_INSERT_LENGTH].asInt();

		currentSegment++;
	}

	return true;
}

bool FileReader::endOfSegment()
{
	if(0 == offset && 0 == delete_length && 0 == insert_length)
	{
		return true;
	}

	return false;
}

int FileReader::read(char* buffer, int len)
{
	if(false == loadSegment())
		return 0;

	int accumulation = 0;
	if(offset > 0)
	{
		int read_length = offset>len?len:offset;
		int count=0;
		do
		{
			count = fs_base->read(buffer+accumulation, read_length);
			read_length -= count;
			accumulation += count;
		}while(count > 0 && read_length > 0);

		offset -= accumulation;
	}

	if(offset == 0 && delete_length > 0)
	{
		delete_length -= ((FileReader*)fs_base)->skip(delete_length);
	}

	if(offset == 0 && insert_length > 0)
	{
		if(accumulation < len)
		{
			int read_length = insert_length>(len-accumulation)?(len-accumulation):insert_length;
			int count=0;
			do
			{
				count = cursor_diff->sgetn(buffer+accumulation, read_length);
				read_length -= count;
				insert_length -= count;
				accumulation += count;
			}while(count > 0 && accumulation < len);
		}
	}

	if(accumulation < len)
	{
		accumulation += read(buffer+accumulation, len-accumulation);
	}

	return accumulation;
}

int FileReader::skip(int len)
{
	if(false == loadSegment())
		return 0;

	int accumulation = 0;
	if(offset > 0)
	{
		int skip_length = offset>len?len:offset;
		fs_base->skip(skip_length);
		accumulation = skip_length;
		offset -= accumulation;
	}

	if(offset == 0 && delete_length > 0)
	{
		((FileReader*)fs_base)->skip(delete_length);
		delete_length = 0;
	}

	if(offset == 0 && insert_length > 0)
	{
		if(accumulation < len)
		{
			int skip_length = insert_length>(len-accumulation)?(len-accumulation):insert_length;
			long cur_pos = fs_diff.tellg();
			long pos = cursor_diff->pubseekoff(skip_length, std::ios::cur, std::ios::in);
			if(fs_diff.eof())
				fs_diff.close();
			long remain = skip_length - (pos - cur_pos);
			insert_length -= skip_length + remain;
			accumulation += (pos - cur_pos);
		}
		else
		{
			insert_length = 0;
			accumulation += insert_length;
		}
	}

	if(accumulation < len)
	{
		accumulation += skip(len - accumulation);
	}

	return accumulation;
}

} /* namespace fs */
